package com.szl.group.web;

import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.szl.group.common.anno.ResponseResult;
import com.szl.group.common.model.R;
import com.szl.group.config.DevPropertiesConfig;
import com.szl.group.exception.BusinessException;
import com.szl.group.manager.EmailModel;
import com.szl.group.manager.MailSendManager;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static com.szl.group.common.enums.ResultCodeEnum.*;

/**
 * description: 处理自定义Error的Controller
 * author: shenxiaolong
 * date: 2020/6/20
 */
@Slf4j
@Api(tags = "异常处理模块")
@RestController
@ControllerAdvice
public class GlobalExceptionController extends AbstractErrorController implements ResponseBodyAdvice<Object> {

    private static final String ERROR_PATH = "/error";

    @Resource
    private MailSendManager mailSendManager;

    @Resource
    private DevPropertiesConfig config;


    public GlobalExceptionController(ErrorAttributes errorAttributes) {
        super(errorAttributes);
    }

    /**
     * @description 自定义业务异常
     */
    @ExceptionHandler(value = BusinessException.class)
    @ResponseStatus(value = HttpStatus.OK)     //服务异常
    public R handleServiceException(HttpServletRequest request, HttpServletResponse response, BusinessException e) {
        e.printStackTrace();
        log.error("捕获异常 code : {},异常信息为 {}", e.getCode(), e.getMessage());
        return R.builder()
                .code(e.getCode())
                .message(e.getMessage())
                .build();
    }



    /**
     * 参数校验失败时异常
     */
    @ExceptionHandler(value = BindException.class)
    public R handleBindException(HttpServletRequest request, HttpServletResponse response, BindException e) {
        StringBuffer message = new StringBuffer();
        e.getBindingResult().getAllErrors()
                .forEach(s -> message.append(s.getDefaultMessage()).append(";"));
        e.printStackTrace();
        log.error("捕获异常 code : {},异常信息为 {}", METHOD_ARGUMENT_NOT_VALID.getCode(), e.getMessage());
        return R.builder()
                .code(METHOD_ARGUMENT_NOT_VALID.getCode())
                .message(message.toString())
                .build();
    }

    /**
     * 系统运行时异常
     */
    @ExceptionHandler(value = Exception.class)
    public R handleRuntimeException(HttpServletRequest request, HttpServletResponse response, Exception e) {
        e.printStackTrace();
        log.error("捕获未知异常 code : {},异常信息为 {}", INTERNAL_SERVER_ERROR.getCode(), e.getMessage());
        return R.builder()
                .code(INTERNAL_SERVER_ERROR.getCode())
                .message(INTERNAL_SERVER_ERROR.getMessage())
                .build();
    }


    /**
     * 空指针异常需要发送邮件通知开发者
     */
    @ExceptionHandler(value = NullPointerException.class)
    public R handleNullPointerException(HttpServletRequest request, HttpServletResponse response, NullPointerException e) {
        e.printStackTrace();
        //发送邮件
        if (config.getOn_off()) {
            sendMail(e);
        }
        log.error("捕获空指针异常 code : {},异常信息为 {}", INTERNAL_SERVER_ERROR.getCode(), e.getMessage());
        return R.builder()
                .code(INTERNAL_SERVER_ERROR.getCode())
                .message(INTERNAL_SERVER_ERROR.getMessage())
                .build();
    }

    private void sendMail(NullPointerException e) {
        mailSendManager.sendEmailAsSysExceptionHtml(getEmailModel(e));
    }

    private EmailModel getEmailModel(NullPointerException e) {
        EmailModel emailModel = new EmailModel();

        emailModel.setEmailTheme("空指针异常");
        emailModel.setEmailContent(ExceptionUtils.getStackTrace(e));

        if (CollectionUtils.isNotEmpty(config.getDev())) {
            List<EmailModel.Dev> collect = config.getDev()
                    .stream()
                    .map(this::builderDev)
                    .collect(Collectors.toList());
            emailModel.setDevs(collect);
        }

        return emailModel;
    }


    private EmailModel.Dev builderDev(Map<String, String> person) {
        return new EmailModel.Dev(person.get("name"), person.get("email"));
    }

    /**
     * 方法参数校验
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    @ResponseBody
    public R handleMethodArgumentNotValidException(HttpServletRequest request,
                                                   HttpServletResponse response,
                                                   ConstraintViolationException e) {
        log.error(e.getMessage(), e);
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        Optional<String> messageOptional = violations.stream()
                .map(ConstraintViolation::getMessageTemplate)
                .findFirst();
        String errorMessage = messageOptional.orElse("系统异常，请稍后尝试");
        return R.fail(METHOD_ARGUMENT_NOT_VALID, errorMessage);
    }


    @RequestMapping(value = ERROR_PATH)
    @ApiOperation(value = "异常处理接口[非外部调用接口]", notes = "异常处理接口[非外部调用接口]")
    public R handleError(HttpServletRequest request) {
        //获取statusCode:401,404,500
        Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
        switch (statusCode) {
            case 400:
                return R.builder()
                        .code(FAILED.getCode())
                        .message(FAILED.getMessage())
                        .build();
            case 401:
                return R.builder()
                        .code(ACCESS_DENIED.getCode())
                        .message(ACCESS_DENIED.getMessage())
                        .build();
            case 403:
                return R.builder()
                        .code(FORBIDDEN.getCode())
                        .message(FORBIDDEN.getMessage())
                        .build();
            case 404:
                return R.builder()
                        .code(NOT_FOUND.getCode())
                        .message(NOT_FOUND.getMessage())
                        .build();
            case 500:
                return R.builder()
                        .code(INTERNAL_SERVER_ERROR.getCode())
                        .message(INTERNAL_SERVER_ERROR.getMessage())
                        .build();
            case 502:
                return R.builder()
                        .code(BAD_GATEWAY.getCode())
                        .message(BAD_GATEWAY.getMessage())
                        .build();
            default:
                return R.builder()
                        .code(UNKNOWN_ERROR.getCode())
                        .message(UNKNOWN_ERROR.getMessage())
                        .build();
        }
    }

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {

        MethodParameter method = methodParameter;
        boolean wrap = method.hasMethodAnnotation(ResponseResult.class);
        if (!wrap) {
            ResponseResult ann = method.getMethod() == null
                    ? null
                    : method.getMethod().getDeclaringClass().getAnnotation(ResponseResult.class);
            if (ann != null) {
                wrap = true;
            }
        }
        return wrap;
    }

    @Override
    public Object beforeBodyWrite(Object data, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {

        return R.builder().code(OK.getCode()).message(OK.getMessage()).data(data).build();
    }
}
